package TrieTree;

/*
单词搜索 II
给定一个m x n 二维字符网格board和一个单词（字符串）列表 words，返回所有二维网格上的单词。
单词必须按照字母顺序，通过 相邻的单元格 内的字母构成，其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。

示例 1：
输入：board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
输出：["eat","oath"]
示例 2：
输入：board = [["a","b"],["c","d"]], words = ["abcb"]
输出：[]

作者：LeetCode
链接：https://leetcode.cn/leetbook/read/trie/x7hd9g/
 */

import java.util.*;

public class _42单词搜索2 {
    public static void main(String[] args) {
        char[][] board = new char[][]{{'o','a','a','n'},{'e','t','a','e'},{'i','h','k','r'},{'i','f','l','v'}};
        char[][] board2 = new char[][]{{'a','a'}};
        String[] words = new String[]{"oath","pea","eat","rain"};
        String[] words2 = new String[]{"aaa"};
        System.out.println(findWords(board, words));

    }

    //BFS 大概走不通
    //BFS的路径是面的推广，因此在排除已经遍历的元素的时候，各个路径会相互影响
    //而DFS的路径是线的推广，在排除已经遍历的元素的时候，各个路径不会相互影响
    //本体应该采用DFS
    public static List<String> findWords(char[][] board, String[] words) {
        
        int m = board.length;
        int n = board[0].length;
        List<String> res = new ArrayList<>();
        for (String word : words) {
            boolean bfs = false;
            char ch = word.charAt(0);
            for (int j = 0; j < m; j++) {
                if(bfs){
                    break;
                }
                for (int k = 0; k < n; k++) {
                    if (ch == board[j][k]) {
                        if(word.length() == 1){
                            res.add(word);
                            bfs = true;
                            break;
                        }
                        Deque<Integer[]> queue = new ArrayDeque<>();
                        HashSet<String> visited = new HashSet<>();
                        queue.add(new Integer[]{j, k});
                        visited.add(j+"_"+k);
                        bfs = bfs(board, word, queue, visited, res);
                        if (bfs) {
                            break;
                        }
                    }
                }
            }
        }

        return res;
    }

    public static boolean bfs(char[][] board, String word, Deque<Integer[]> queue, HashSet<String> visited, List<String> res) {
        int m = board.length;
        int n = board[0].length;

        for (int i = 1; i < word.length(); i++) {
            char ch = word.charAt(i);

            int size = queue.size();
            if (size == 0) {
                break;
            }
            while (size-- > 0) {
                Integer[] poll = queue.poll();
                int x = poll[1];
                int y = poll[0];
                int left = x - 1;
                int right = x + 1;
                int up = y - 1;
                int down = y + 1;
                if (left != -1 && board[y][left] == ch) {
                    if (!visited.contains(y+"_"+left)) {
                        if (i == word.length() - 1) {
                            res.add(word);
                            return true;
                        }
                        queue.add(new Integer[]{y, left});
                        visited.add(y+"_"+left);
                    }
                }
                if (right != n && board[y][right] == ch) {
                    if (!visited.contains(y+"_"+right)) {
                        if (i == word.length() - 1) {
                            res.add(word);
                            return true;
                        }
                        queue.add(new Integer[]{y, right});
                        visited.add(y+"_"+right);
                    }
                }
                if (up != -1 && board[up][x] == ch) {
                    if (!visited.contains(up+"_"+x)) {
                        if (i == word.length() - 1) {
                            res.add(word);
                            return true;
                        }
                        queue.add(new Integer[]{up, x});
                        visited.add(up+"_"+x);
                    }
                }
                if (down != m && board[down][x] == ch) {
                    if (!visited.contains(down+"_"+x)) {
                        if (i == word.length() - 1) {
                            res.add(word);

                        }
                        queue.add(new Integer[]{down, x});
                        visited.add(down+"_"+x);
                    }
                }
            }
        }
        return false;
    }



    //回溯(一般指DFS) + 字典树
    //将words加入字典树中
    //将board化作一维数组，进行DFS回溯遍历 字典树
    //不断判断可能的路径
    //链接：https://leetcode.cn/problems/word-search-ii/solutions/1000172/dan-ci-sou-suo-ii-by-leetcode-solution-7494/
    class Solution {
        int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

        public List<String> findWords(char[][] board, String[] words) {
            Trie trie = new Trie();
            for (String word : words) {
                trie.insert(word);
            }

            Set<String> ans = new HashSet<String>();
            for (int i = 0; i < board.length; ++i) {
                for (int j = 0; j < board[0].length; ++j) {
                    dfs(board, trie, i, j, ans);
                }
            }

            return new ArrayList<String>(ans);
        }

        public void dfs(char[][] board, Trie now, int i1, int j1, Set<String> ans) {
            if (!now.children.containsKey(board[i1][j1])) {
                return;
            }
            char ch = board[i1][j1];
            now = now.children.get(ch);
            if (!"".equals(now.word)) {
                ans.add(now.word);
            }

            board[i1][j1] = '#';
            for (int[] dir : dirs) {
                int i2 = i1 + dir[0], j2 = j1 + dir[1];
                if (i2 >= 0 && i2 < board.length && j2 >= 0 && j2 < board[0].length) {
                    dfs(board, now, i2, j2, ans);
                }
            }
            board[i1][j1] = ch;
        }
    }

    class Trie {
        String word;
        Map<Character, Trie> children;
        boolean isWord;

        public Trie() {
            this.word = "";
            this.children = new HashMap<Character, Trie>();
        }

        public void insert(String word) {
            Trie cur = this;
            for (int i = 0; i < word.length(); ++i) {
                char c = word.charAt(i);
                if (!cur.children.containsKey(c)) {
                    cur.children.put(c, new Trie());
                }
                cur = cur.children.get(c);
            }
            cur.word = word;
        }
    }

    //官解二
    //官解一优化 删除已经遍历过的路径
//    链接：https://leetcode.cn/problems/word-search-ii/solutions/1000172/dan-ci-sou-suo-ii-by-leetcode-solution-7494/
    class Solution2 {
        int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

        public List<String> findWords(char[][] board, String[] words) {
            Trie trie = new Trie();
            for (String word : words) {
                trie.insert(word);
            }

            Set<String> ans = new HashSet<String>();
            for (int i = 0; i < board.length; ++i) {
                for (int j = 0; j < board[0].length; ++j) {
                    dfs(board, trie, i, j, ans);
                }
            }

            return new ArrayList<String>(ans);
        }

        public void dfs(char[][] board, Trie now, int i1, int j1, Set<String> ans) {
            if (!now.children.containsKey(board[i1][j1])) {
                return;
            }
            char ch = board[i1][j1];
            Trie nxt = now.children.get(ch);
            if (!"".equals(nxt.word)) {
                ans.add(nxt.word);
                nxt.word = "";
            }

            if (!nxt.children.isEmpty()) {
                board[i1][j1] = '#';
                for (int[] dir : dirs) {
                    int i2 = i1 + dir[0], j2 = j1 + dir[1];
                    if (i2 >= 0 && i2 < board.length && j2 >= 0 && j2 < board[0].length) {
                        dfs(board, nxt, i2, j2, ans);
                    }
                }
                board[i1][j1] = ch;
            }

            //当一条Trie走到底的时候就删除该结点
            //如果是单词末尾以后就不会再遍历到
            //反复走这条路径，就会反复删除该路径的最后一个结点，直到完全没有，节省了多次出现同一个路径的情况
            if (nxt.children.isEmpty()) {
                now.children.remove(ch);
            }
        }

        class Trie {
            String word;
            Map<Character,Trie> children;
            boolean isWord;

            public Trie() {
                this.word = "";
                this.children = new HashMap<Character,Trie>();
            }

            public void insert(String word) {
                Trie cur = this;
                for (int i = 0; i < word.length(); ++i) {
                    char c = word.charAt(i);
                    if (!cur.children.containsKey(c)) {
                        cur.children.put(c, new Trie());
                    }
                    cur = cur.children.get(c);
                }
                cur.word = word;
            }
        }
    }




}
